--清除屏幕
term.clear()
--常量数据初始化
local proxy_channel = 0
local yaw_name = ""
local camera_name = ""
local cannon_direction = "North"
local yaw_correct = 0.0
local y_correct = 0.0
local k = 8
local n = 8
local pitch_invert = 1
local error = ""
local current_pitch = 0
local current_yaw
local mode = "Camera"
local fire = false
local fix_targetpos = {x = 0 , y = 0 , z = 0}
local input = false
local out_of_range = false
local pitch_control = peripheral.wrap("back")
pitch_control.setTargetSpeed(0)
local front_yaw = nil
--函数
function sec(i)
    local seci = 1 / math.cos(math.rad(i))
    return seci
end
function tan(i)
    local tani = math.tan(math.rad(i))
    return tani
end
function round(input , num)
    local remain = input % num
    if remain < 0 then
        return input - remain - num
    elseif remain > 0 then
        return input - remain
    else
        return input
    end
end
--初始化、错误重置屏幕
function screen_set()
    --文字颜色设置
    term.setTextColor(1)
    --光标关闭
    term.setCursorBlink(false)
    --背景色
    paintutils.drawFilledBox(1 , 1 , 51 , 19 , 2048)
    term.setCursorPos(20 , 1)
    term.write("FIRE CONTROL")
    --文字部分
    term.setCursorPos(5 , 3)
    term.write("Proxy channel:")
    term.setCursorPos(30 , 3)
    term.write("Barrel length:")
    term.setCursorPos(5 , 5)
    term.write("Vertical correction:")
    term.setCursorPos(31 , 5)
    term.write("Powder power:")
    term.setCursorPos(5 , 7)
    term.write("Yaw proxy name:")
    term.setCursorPos(5 , 9)
    term.write("Camera proxy name:")
    term.setCursorPos(5 , 11)
    term.write("Cannon direction:  North  East  South  West")
    term.setCursorPos(5 , 13)
    term.write("Pitch invertion:        Normal       Invert")
    term.setCursorPos(5 , 16)
    term.write("Camera")
    term.setCursorPos(5 , 18)
    term.write("Manual")
    --文字框背景色部分
    paintutils.drawFilledBox(20 , 3 , 22 , 3 , 256)
    paintutils.drawFilledBox(45 , 3 , 47 , 3 , 256)
    paintutils.drawFilledBox(26 , 5 , 28 , 5 , 256)
    paintutils.drawFilledBox(45 , 5 , 47 , 5 , 256)
    paintutils.drawFilledBox(21 , 7 , 47 , 7 , 256)
    paintutils.drawFilledBox(24 , 9 , 47 , 9 , 256)
    --选择部分
    term.setTextColor(32768)
    term.setBackgroundColor(16384)
    if cannon_direction == "North" then
        term.setCursorPos(24 , 11)
        term.write("North")
    elseif cannon_direction == "East" then
        term.setCursorPos(31 , 11)
        term.write("East")
    elseif cannon_direction == "South" then
        term.setCursorPos(37 , 11)
        term.write("South")
    elseif cannon_direction == "West" then
        term.setCursorPos(44 , 11)
        term.write("West")
    end
    if pitch_invert == 1 then
        term.setCursorPos(29 , 13)
        term.write("Normal")
    elseif pitch_invert == -1 then
        term.setCursorPos(42 , 13)
        term.write("Invert")
    end
    --模式选择
    if mode == "Camera" then
        term.setCursorPos(5 , 16)
        term.write("Camera")
    elseif mode == "Manual" then
        term.setCursorPos(5 , 18)
        term.write("Manual")
        term.setBackgroundColor(2048)
        term.setTextColor(1)
        term.setCursorPos(13 , 15)
        term.write("X:")
        term.setCursorPos(13 , 17)
        term.write("Y:")
        term.setCursorPos(13 , 19)
        term.write("Z:")
        paintutils.drawFilledBox(16 , 15 , 20 , 15 , 256)
        paintutils.drawFilledBox(16 , 17 , 20 , 17 , 256)
        paintutils.drawFilledBox(16 , 19 , 20 , 19 , 256)
        --开火按键
        if fire == false then
            paintutils.drawFilledBox(23 , 16 , 31 , 18 , 128)
            term.setTextColor(256)
            term.setCursorPos(25 , 17)
            term.write("FIRE!")
        elseif fire == true then
            paintutils.drawFilledBox(23 , 16 , 31 , 18 , 16384)
            term.setTextColor(1)
            term.setCursorPos(25 , 17)
            term.write("FIRE!")
        end
    end
    --文字、背景颜色重置
    term.setBackgroundColor(256)
    term.setTextColor(1)
    --初始数据录入
    term.setCursorPos(20 , 3)
    term.write(proxy_channel)
    term.setCursorPos(45 , 3)
    term.write(k)
    term.setCursorPos(26 , 5)
    term.write(y_correct)
    term.setCursorPos(45 , 5)
    term.write(n)
    term.setCursorPos(21 , 7)
    term.write(yaw_name)
    term.setCursorPos(24 , 9)
    term.write(camera_name)
    if mode == "Manual" then
        term.setCursorPos(16 , 15)
        term.write(fix_targetpos.x)
        term.setCursorPos(16 , 17)
        term.write(fix_targetpos.y)
        term.setCursorPos(16 , 19)
        term.write(fix_targetpos.z)
    end
end
--错误弹窗
function error_display()
    local error_output = ""
    if error == "format" then
        error_output = "Wrong format"
    elseif error == "length" then
        error_output = "Input too long"
    end
    term.setCursorBlink(false)
    term.setCursorPos(34 , 16)
    term.setBackgroundColor(2048)
    term.setTextColor(16384)
    term.write(error_output)
    sleep(1.5)
    error = ""
end
--触摸屏功能
function touch_screen()
    local event , bottom , x , y = os.pullEvent("mouse_click")
    if bottom == 1 then
        input = true
        if x >= 20 and x <= 22 and y == 3 then
            paintutils.drawFilledBox(20 , 3 , 22 , 3 , 256)
            term.setCursorPos(20 , 3)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" or temp % 1 ~= 0 or temp < 0 then
                error = "format"
            elseif string.len(temp) > 3 then
                error = "length"
            else
                proxy_channel = temp
            end
        elseif x >= 45 and x <= 47 and y == 3 then
            paintutils.drawFilledBox(45 , 3 , 47 , 3 , 256)
            term.setCursorPos(45 , 3)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" or temp % 1 ~= 0 or temp <= 0 then
                error = "format"
            elseif string.len(temp) > 3 then
                error = "length"
            else
                k = temp
            end
        elseif x >= 26 and x <= 28 and y == 5 then
            paintutils.drawFilledBox(26 , 5 , 28 , 5 , 256)
            term.setCursorPos(26 , 5)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" then
                error = "format"
            elseif string.len(temp) > 3 then
                error = "length"
            else
                y_correct = temp
            end
        elseif x >= 45 and x <= 47 and y == 5 then
            paintutils.drawFilledBox(45 , 5 , 47 , 5 , 256)
            term.setCursorPos(45 , 5)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" or temp % 1 ~= 0 or temp <= 0 then
                error = "format"
            elseif string.len(temp) > 3 then
                error = "length"
            else
                n = temp
            end
        elseif x >= 21 and x <= 47 and y == 7 then
            paintutils.drawFilledBox(21 , 7 , 47 , 7 , 256)
            term.setCursorPos(21 , 7)
            term.setCursorBlink(true)
            local temp = tostring(io.read())
            if string.len(temp) > 27 then
                error = "length"
            else
                yaw_name = temp
            end
        elseif x >= 24 and x <= 47 and y == 9 then
            paintutils.drawFilledBox(24 , 9 , 47 , 9 , 256)
            term.setCursorPos(24 , 9)
            term.setCursorBlink(true)
            local temp = tostring(io.read())
            if string.len(temp) > 24 then
                error = "length"
            else
                camera_name = temp
            end
        elseif x >= 16 and x <= 20 and y == 15 and mode == "Manual" then
            paintutils.drawFilledBox(16 , 15 , 20 , 15 , 256)
            term.setCursorPos(16 , 15)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" then
                error = "format"
            elseif string.len(temp) > 5 then
                error = "length"
            else
                fix_targetpos.x = temp
            end
        elseif x >= 16 and x <= 20 and y == 17 and mode == "Manual" then
            paintutils.drawFilledBox(16 , 17 , 20 , 17 , 256)
            term.setCursorPos(16 , 17)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" then
                error = "format"
            elseif string.len(temp) > 5 then
                error = "length"
            else
                fix_targetpos.y = temp
            end
        elseif x >= 16 and x <= 20 and y == 19 and mode == "Manual" then
            paintutils.drawFilledBox(16 , 19 , 20 , 19 , 256)
            term.setCursorPos(16 , 19)
            term.setCursorBlink(true)
            local temp = tonumber(io.read())
            if type(temp) ~= "number" then
                error = "format"
            elseif string.len(temp) > 5 then
                error = "length"
            else
                fix_targetpos.z = temp
            end
        elseif x >= 24 and x <= 28 and y == 11 then
            cannon_direction = "North"
            yaw_correct = 0
        elseif x >= 31 and x <= 34 and y == 11 then
            cannon_direction = "East"
            yaw_correct = -math.pi / 2
        elseif x >= 37 and x <= 41 and y == 11 then
            cannon_direction = "South"
            yaw_correct = math.pi
        elseif x >= 44 and x <= 47 and y == 11 then
            cannon_direction = "West"
            yaw_correct = math.pi / 2
        elseif x >= 29 and x <= 34 and y == 13 then
            pitch_invert = 1
        elseif x >= 42 and x <= 47 and y == 13 then
            pitch_invert = -1
        elseif x >= 5 and x <= 10 and y == 16 then
            mode = "Camera"
        elseif x >= 5 and x <= 10 and y == 18 then
            mode = "Manual"
        elseif x >= 23 and x <= 31 and y >= 16 and y <= 18 and mode == "Manual" and fire == true then
            redstone.setOutput("bottom" , true)
            fire = false
            screen_set()
            sleep(1)
            redstone.setOutput("bottom" , false)
            sleep(2)
            fire = true
            screen_set()
        end
        if error ~= "" then
            error_display()
        end
        screen_set()
        input = false
    end
end
--惯导校准
function alignment()
    ::start::
    sleep(0.05)
    while input == true do
        sleep(0.05)
    end
    local proxy = peripheral.find("transmitter")
    if proxy == nil then
        term.setBackgroundColor(2048)
        term.setTextColor(16384)
        term.setCursorPos(34 , 18)
        term.write("Proxy offline")
        term.setBackgroundColor(256)
        term.setTextColor(1)
        goto start
    else
        paintutils.drawFilledBox(34 , 18 , 46 , 18 , 2048)
        term.setBackgroundColor(256)
        term.setTextColor(1)
    end
    proxy.setProtocol(proxy_channel)
    proxy.callRemote(yaw_name , "setMode" , true)
    proxy.callRemote(yaw_name , "setTargetAngle" , 0)
    local motor_angle = proxy.callRemote(yaw_name , "getAngle")
    if motor_angle == nil then
        term.setBackgroundColor(2048)
        term.setTextColor(16384)
        term.setCursorPos(34 , 18)
        term.write("Motor offline")
        term.setBackgroundColor(256)
        term.setTextColor(1)
        goto start
    else
        term.setBackgroundColor(2048)
        term.setTextColor(1)
        term.setCursorPos(34 , 18)
        term.write("Aligning...")
        term.setBackgroundColor(256)
    end
    if math.abs(motor_angle) <= 1e-3 then
        front_yaw = ship.getYaw()
    end
end
--弹道计算部分
function calculator()
    ::start::
    sleep(0.05)
    while input == true do
        sleep(0.05)
    end
    local cannonpitch = nil
    local cannonyaw = nil
    local proxy = peripheral.find("transmitter")
    if proxy == nil then
        term.setBackgroundColor(2048)
        term.setTextColor(16384)
        term.setCursorPos(34 , 18)
        term.write("Proxy offline")
        term.setBackgroundColor(256)
        term.setTextColor(1)
        goto start
    else
        paintutils.drawFilledBox(34 , 18 , 46 , 18 , 2048)
        term.setBackgroundColor(256)
        term.setTextColor(1)
    end
    --获取船体重心世界坐标
    local pos = ship.getWorldspacePosition()
    --获取船体偏航
    local yaw = ship.getYaw()
    proxy.setProtocol(proxy_channel)
    proxy.callRemote(yaw_name , "setMode" , true)
    local motor_angle = proxy.callRemote(yaw_name , "getAngle")
    if motor_angle == nil then
        term.setBackgroundColor(2048)
        term.setTextColor(16384)
        term.setCursorPos(34 , 18)
        term.write("Motor offline")
        term.setBackgroundColor(256)
        term.setTextColor(1)
        goto start
    else
        paintutils.drawFilledBox(34 , 18 , 51 , 18 , 2048)
        term.setBackgroundColor(256)
        term.setTextColor(1)
        front_yaw = yaw - motor_angle
    end
    --炮管轴世界坐标
    local cannonpos = {x = pos.x , y = pos.y + y_correct , z = pos.z}
    local targetpos = {x = nil , y = nil , z = nil}
    if mode == "Camera" then  --头瞄模式
        local camerapos = proxy.callRemote(camera_name , "clip")
        if camerapos == nil then
            term.setBackgroundColor(2048)
            term.setTextColor(16384)
            term.setCursorPos(34 , 18)
            term.write("Camera offline")
            term.setBackgroundColor(256)
            term.setTextColor(1)
            goto start
        else
            paintutils.drawFilledBox(34 , 18 , 47 , 18 , 2048)
            term.setBackgroundColor(256)
            term.setTextColor(1)
        end
        targetpos = camerapos.hit
    elseif mode == "Manual" then  --坐标点模式
        targetpos = fix_targetpos
    end
    --炮塔对目标世界坐标系相对距离
    local delta = {
        x = targetpos.x - cannonpos.x ,
        y = targetpos.y - cannonpos.y ,
        z = targetpos.z - cannonpos.z
    }
    --直线水平距离
    local w = math.sqrt(delta.x ^ 2 + delta.z ^ 2)
    --俯仰计算
    local miss = 0.2
    repeat
        for i = 60 , -10 , -0.375 do
            local y_ccl = ((5 * sec(i) / n) + tan(i)) * w + 500 * math.log(1 - (w * sec(i) - k) / (100 * n)) - 5 * k / n
            if math.abs(delta.y - y_ccl) <= miss and miss < 2 then --误差内选取
                cannonpitch = i
                break
            elseif miss >= 2 then --误差外返回信息
                cannonpitch = "out of range"
                break
            end
        end
        miss = miss + 0.2
    until cannonpitch ~= nil
    if cannonpitch == "out of range" then
        term.setBackgroundColor(2048)
        term.setTextColor(16384)
        term.setCursorPos(34 , 17)
        term.write("Out of range")
        term.setBackgroundColor(256)
        term.setTextColor(1)
        goto start
    else
        paintutils.drawFilledBox(34 , 17 , 51 , 17 , 2048)
        term.setBackgroundColor(256)
        term.setTextColor(1)
    end
    --偏航计算
    cannonyaw = math.atan2(delta.z , delta.x)
    if cannonyaw > 0 then
        cannonyaw = cannonyaw - math.pi
    else
        cannonyaw = cannonyaw + math.pi
    end
    cannonyaw = cannonyaw + yaw_correct
    --运动输出
    local true_yaw = cannonyaw - front_yaw
    local deltapitch = cannonpitch - current_pitch
    proxy.callRemote(yaw_name , "setTargetAngle" , true_yaw)
    local pitchoutput = pitch_invert * math.floor(deltapitch / 0.0375)
    if pitchoutput > 256 then
        pitchoutput = 256
        deltapitch = 9.6
    elseif pitchoutput < -256 then
        pitchoutput = -256
        deltapitch = -9.6
    end
    pitch_control.setTargetSpeed(pitchoutput)
    current_pitch = current_pitch + deltapitch
    pitch_control.setTargetSpeed(0)
    --偏航误差确认
    local deltayaw = math.abs(true_yaw - proxy.callRemote(yaw_name , "getAngle"))
    if deltayaw <= 1e-3 then
        fire = true
    else
        fire = false
    end
    screen_set()
end
--图形化界面初始化
screen_set()
--朝向校准
while front_yaw == nil do
    parallel.waitForAny(touch_screen , alignment)
end
screen_set()
--主功能
while true do
    parallel.waitForAny(touch_screen , calculator)
end